/*
StickyInput by Jonathan Dearborn
This file is licensed under the TSL.
See StickyInput.h for details.
*/

#include "StickyInput.h"
#include <cmath>

namespace SI
{

unsigned int numjoysticks = 0;
SDL_Joystick** joysticks = NULL;
Uint8* keystates = NULL;
bool wasinit = false;


inline bool inrange(float low, float value, float high)
{
    return (low <= value && value <= high);
}

void Reset(bool print)
{
    wasinit = false;
    numjoysticks = SDL_NumJoysticks();

    if(numjoysticks > 0)
    {
        if(print)
            printf("Closing %d joystick%s...\n", numjoysticks, (numjoysticks > 1? "s" : ""));

        for(unsigned int i = 0; i < numjoysticks; i++ )
        {
            if(print)
                printf("%d.", i+1);
            SDL_JoystickClose(SDL_JoystickOpen(i));
        }
        if(print)
            printf("\n");

    }

    if(SDL_WasInit(SDL_INIT_JOYSTICK) & SDL_INIT_JOYSTICK)
        SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
    delete[] joysticks;
    joysticks = NULL;

    // Init joysticks
    SDL_InitSubSystem(SDL_INIT_JOYSTICK);

    numjoysticks = SDL_NumJoysticks();
    if(numjoysticks > 0)
    {
        if(print)
        {
            printf("Opening joysticks: %d found...\n", numjoysticks);
        }

        joysticks = new SDL_Joystick*[numjoysticks];

        for(unsigned int i = 0; i < numjoysticks; i++ )
        {
            if(print)
            {
                printf("    \"%s\"...", SDL_JoystickName(i));
                joysticks[i] = SDL_JoystickOpen(i);
                if(joysticks[i] != NULL)
                    printf("  Ok\n");
                else
                    printf("  Failed\n");
            }
            else
                joysticks[i] = SDL_JoystickOpen(i);
        }

    }
    else if(print)
        printf("Opening joysticks: None found\n");

    // Init key holding
    keystates = SDL_GetKeyState(NULL);
    wasinit = true;
}

void PrintButton(const Button& button)
{
    switch(button.type)
    {
        case Button::KEY:
        printf("Key: %s\n", SDL_GetKeyName(button.key));
        break;
        case Button::MOUSEBUTTON:
        printf("Mouse #%d: Button %d\n", button.mouse, button.mouseButton);
        break;
        case Button::JOYBUTTON:
        printf("Joystick #%d: Button %d\n", button.joystick, button.joyItem);
        break;
        case Button::JOYAXIS_NEG:
        printf("Joystick #%d: Neg Axis %d\n", button.joystick, button.joyItem);
        break;
        case Button::JOYAXIS_POS:
        printf("Joystick #%d: Pos Axis %d\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_CENTER:
        printf("Joystick #%d: Hat %d Center\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_UP:
        printf("Joystick #%d: Hat %d Up\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_DOWN:
        printf("Joystick #%d: Hat %d Down\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_LEFT:
        printf("Joystick #%d: Hat %d Left\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_RIGHT:
        printf("Joystick #%d: Hat %d Right\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_LEFTUP:
        printf("Joystick #%d: Hat %d LeftUp\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_LEFTDOWN:
        printf("Joystick #%d: Hat %d LeftDown\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_RIGHTUP:
        printf("Joystick #%d: Hat %d RightUp\n", button.joystick, button.joyItem);
        break;
        case Button::JOYHAT_RIGHTDOWN:
        printf("Joystick #%d: Hat %d RightDown\n", button.joystick, button.joyItem);
        break;
        default:
        break;
    }
}


/*
MonitorInput() runs a loop, waiting for input events.  When an event
happens, the information is sent to the function that is given as an argument.
That function then should present the information to the user somehow.
A sample function is provided, PrintButton().
When SDLK_ESCAPE is pressed, the loop is ended and the function returns.
*/
void MonitorInput(void (*fn)(const Button&), int deadZone)
{
    if(fn == NULL)
        return;

    Button result(SDLK_UNKNOWN);

    SDL_Event event;
    bool done = false;
    while(!done)
    {
        while(SDL_PollEvent(&event))
        {
            if(event.type == SDL_QUIT)
            {
                done = true;
            }
            else if(event.type == SDL_KEYDOWN)
            {
                if(event.key.keysym.sym == SDLK_ESCAPE)
                    done = true;
                else
                {
                    result.type = Button::KEY;
                    result.key = event.key.keysym.sym;
                    fn(result);
                }
            }
            else if(event.type == SDL_JOYAXISMOTION)
            {
                result.joystick = event.jaxis.which;
                result.joyItem = event.jaxis.axis;
                if(event.jaxis.value < -deadZone)
                {
                    result.type = Button::JOYAXIS_NEG;
                    fn(result);
                }
                else if(event.jaxis.value > deadZone)
                {
                    result.type = Button::JOYAXIS_POS;
                    fn(result);
                }
            }
            else if(event.type == SDL_JOYBUTTONDOWN)
            {
                result.type = Button::JOYBUTTON;
                result.joystick = event.jbutton.which;
                result.joyItem = event.jbutton.button;

                fn(result);
            }
            else if(event.type == SDL_MOUSEBUTTONDOWN)
            {
                result.type = Button::MOUSEBUTTON;
                result.mouse = 0;
                result.mouseButton = event.button.button;

                fn(result);
            }
            else if(event.type == SDL_JOYHATMOTION)
            {
                result.type = Button::JOYHAT_CENTER;
                if((event.jhat.value & SDL_HAT_LEFTUP) == SDL_HAT_LEFTUP)
                    result.type = Button::JOYHAT_LEFTUP;
                if((event.jhat.value & SDL_HAT_LEFTDOWN) == SDL_HAT_LEFTDOWN)
                    result.type = Button::JOYHAT_LEFTDOWN;
                if((event.jhat.value & SDL_HAT_RIGHTUP) == SDL_HAT_RIGHTUP)
                    result.type = Button::JOYHAT_RIGHTUP;
                if((event.jhat.value & SDL_HAT_RIGHTDOWN) == SDL_HAT_RIGHTDOWN)
                    result.type = Button::JOYHAT_RIGHTDOWN;
                if(event.jhat.value & SDL_HAT_UP)
                    result.type = Button::JOYHAT_UP;
                if(event.jhat.value & SDL_HAT_DOWN)
                    result.type = Button::JOYHAT_DOWN;
                if(event.jhat.value & SDL_HAT_LEFT)
                    result.type = Button::JOYHAT_LEFT;
                if(event.jhat.value & SDL_HAT_RIGHT)
                    result.type = Button::JOYHAT_RIGHT;
                result.joystick = event.jhat.which;
                result.joyItem = event.jhat.hat;

                if(result.type != Button::JOYHAT_CENTER)
                    fn(result);
            }
        }


        SDL_Delay(100);
    }
}



/*
WaitPress() waits in a loop until a valid input 'button' is pressed.
These 'buttons' can be a keyboard key or any axis/button/hat/ball
on a joystick.  If the optional pointer to a Button object is filled in, then
that will be set to the same value as the return value.
If 'waitTime' is nonzero, the function will return after that many milliseconds.
*/
Button WaitPress(Button* button, int deadZone, Uint32 waitTime, Uint32 delay)
{
    Button result(SDLK_UNKNOWN);
    result.deadZone = deadZone;

    // Clear event queue
    SDL_Event event;
    while(SDL_PollEvent(&event));
    
    Uint32 beginWait = SDL_GetTicks();

    bool done = false;
    while(!done)
    {
        while(SDL_PollEvent(&event))
        {
            if(event.type == SDL_QUIT)
            {
                done = true;
                break;
            }
            else if(event.type == SDL_KEYDOWN)
            {
                result.type = Button::KEY;
                result.key = event.key.keysym.sym;
                done = true;
                break;
            }
            else if(event.type == SDL_JOYAXISMOTION)
            {
                result.joystick = event.jaxis.which;
                result.joyItem = event.jaxis.axis;
                if(event.jaxis.value < -deadZone)
                {
                    result.type = Button::JOYAXIS_NEG;
                    done = true;
                    break;
                }
                else if(event.jaxis.value > deadZone)
                {
                    result.type = Button::JOYAXIS_POS;
                    done = true;
                    break;
                }
            }
            else if(event.type == SDL_JOYBUTTONDOWN)
            {
                result.type = Button::JOYBUTTON;
                result.joystick = event.jbutton.which;
                result.joyItem = event.jbutton.button;
                done = true;
                break;
            }
            else if(event.type == SDL_MOUSEBUTTONDOWN)
            {
                result.type = Button::MOUSEBUTTON;
                result.mouse = 0;
                result.mouseButton = event.button.button;

                done = true;
                break;
            }
            else if(event.type == SDL_JOYHATMOTION)
            {
                result.type = Button::JOYHAT_CENTER;
                if((event.jhat.value & SDL_HAT_LEFTUP) == SDL_HAT_LEFTUP)
                    result.type = Button::JOYHAT_LEFTUP;
                if((event.jhat.value & SDL_HAT_LEFTDOWN) == SDL_HAT_LEFTDOWN)
                    result.type = Button::JOYHAT_LEFTDOWN;
                if((event.jhat.value & SDL_HAT_RIGHTUP) == SDL_HAT_RIGHTUP)
                    result.type = Button::JOYHAT_RIGHTUP;
                if((event.jhat.value & SDL_HAT_RIGHTDOWN) == SDL_HAT_RIGHTDOWN)
                    result.type = Button::JOYHAT_RIGHTDOWN;
                if(event.jhat.value & SDL_HAT_UP)
                    result.type = Button::JOYHAT_UP;
                if(event.jhat.value & SDL_HAT_DOWN)
                    result.type = Button::JOYHAT_DOWN;
                if(event.jhat.value & SDL_HAT_LEFT)
                    result.type = Button::JOYHAT_LEFT;
                if(event.jhat.value & SDL_HAT_RIGHT)
                    result.type = Button::JOYHAT_RIGHT;
                result.joystick = event.jhat.which;
                result.joyItem = event.jhat.hat;
                
                if(result.type != Button::JOYHAT_CENTER)
                {
                    done = true;
                    break;
                }
            }
        }

        SDL_Delay(delay);
        if(waitTime > 0 && SDL_GetTicks() - beginWait > waitTime)
            done = true;
    }


    if(button != NULL)
        *button = result;
    
    return result;
}



// JoystickInfo class

bool JoystickInfo::load(unsigned int index)
{
    if(index < numjoysticks)
    {
        this->index = index;
        joystick = joysticks[index];
        name = SDL_JoystickName(index);
        axes = SDL_JoystickNumAxes(joystick);
        buttons = SDL_JoystickNumButtons(joystick);
        hats = SDL_JoystickNumHats(joystick);
        balls = SDL_JoystickNumBalls(joystick);
        return true;
    }
    else
    {
        this->index = 0;
        joystick = NULL;
        name = NULL;
        axes = 0;
        buttons = 0;
        hats = 0;
        balls = 0;
        return false;
    }
}



// Button class


Button::Button()
    : type(KEY)
    , joystick(-1)
    , joyItem(-1)
    , deadZone(100)
    , oldAxis(0)
    , oldHat(SDL_HAT_CENTERED)
    , distinctHatDirections(false)
    , key(SDLK_UNKNOWN)
    , mouse(0)
    , mouseButton(0)
{}
Button::Button(SDLKey key)
    : type(KEY)
    , joystick(-1)
    , joyItem(-1)
    , deadZone(100)
    , oldAxis(0)
    , oldHat(SDL_HAT_CENTERED)
    , distinctHatDirections(false)
    , key(key)
    , mouse(0)
    , mouseButton(0)
{}
Button::Button(TypeEnum type, int joystick, int buttonIndex, int deadZone)
    : type(type)
    , joystick(joystick)
    , joyItem(buttonIndex)
    , deadZone(deadZone)
    , oldAxis(0)
    , oldHat(SDL_HAT_CENTERED)
    , distinctHatDirections(false)
    , key(SDLK_UNKNOWN)
    , mouse(0)
    , mouseButton(0)
{}
// When I implement multiple mice, this constructor needs to be differentiated from the joystick one.
// Or I should unite the constructors and test the type.
Button::Button(TypeEnum type, /*unsigned int mouseIndex,*/ unsigned int buttonIndex)
    : type(type)
    , joystick(-1)
    , joyItem(-1)
    , deadZone(100)
    , oldAxis(0)
    , oldHat(SDL_HAT_CENTERED)
    , distinctHatDirections(false)
    , key(SDLK_UNKNOWN)
    , mouse(0)
    , mouseButton(buttonIndex)
{}
/*Button::Button(const Button& button)
    : type(button.type)
    , joystick(button.joystick)
    , joyItem(button.joyItem)
    , deadZone(button.deadZone)
    , oldAxis(button.oldAxis)
    , oldHat(button.oldHat)
    , distinctHatDirections(button.distinctHatDirections)
    , key(button.key)
    , mouse(button.mouse)
    , mouseButton(button.mouseButton)
{}

Button& Button::operator=(const Button& button)
{
    type = button.type;
    joystick = button.joystick;
    joyItem = button.joyItem;
    deadZone = button.deadZone;
    oldAxis = button.oldAxis;
    oldHat = button.oldHat;
    distinctHatDirections = button.distinctHatDirections;
    key = button.key;
    mouse = button.mouse;
    mouseButton = button.mouseButton;
}*/








// Very similar to getState(), but checks against deadZone too.
bool Button::checkHold()
{
    if(type != KEY && type != MOUSEBUTTON && (joystick < 0 || joystick >= numjoysticks || joysticks == NULL))
        return false;

    switch(type)
    {
        case KEY:
            return (keystates != NULL && keystates[key]);

        case MOUSEBUTTON:
            printf("Mouse: %d, %d", SDL_GetMouseState(NULL, NULL), SDL_BUTTON(mouseButton));
            return (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(mouseButton));

        case JOYBUTTON:
            return SDL_JoystickGetButton(joysticks[joystick], joyItem);

        case JOYAXIS_NEG:
            //printf("Joyaxis Neg: %d (%d)\n", SDL_JoystickGetAxis(joysticks[joystick], joyItem), joyItem);
            return (SDL_JoystickGetAxis(joysticks[joystick], joyItem) <= -deadZone);
        case JOYAXIS_POS:
            return (SDL_JoystickGetAxis(joysticks[joystick], joyItem) >= deadZone);

        case JOYHAT_UP:
            return (distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_UP : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_UP);
        case JOYHAT_DOWN:
            return (distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_DOWN : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_DOWN);
        case JOYHAT_LEFT:
            return (distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_LEFT : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_LEFT);
        case JOYHAT_RIGHT:
            return (distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_RIGHT : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_RIGHT);
        case JOYHAT_LEFTUP:
            return (distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_LEFTUP : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_LEFTUP);
        case JOYHAT_LEFTDOWN:
            return (distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_LEFTDOWN : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_LEFTDOWN);
        case JOYHAT_RIGHTUP:
            return (distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_RIGHTUP : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_RIGHTUP);
        case JOYHAT_RIGHTDOWN:
            return (distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_RIGHTDOWN : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_RIGHTDOWN);
        default:
            break;
    }
    return false;
}

bool Button::checkDown(const SDL_Event& event)
{
    if(event.type == SDL_KEYDOWN)
    {
        return (type == KEY && event.key.keysym.sym == key);
    }
    if(event.type == SDL_MOUSEBUTTONDOWN)
    {
        return (type == MOUSEBUTTON && /*event.button.which == mouse &&*/ event.button.button == mouseButton);
    }

    if(joystick < 0 || joystick >= numjoysticks || joysticks == NULL)
        return false;

    if(event.type == SDL_JOYBUTTONDOWN)
    {
        return (type == JOYBUTTON && joystick == event.jbutton.which && event.jbutton.button == joyItem);
    }
    if(event.type == SDL_JOYAXISMOTION)
    {
        if((type == JOYAXIS_NEG || type == JOYAXIS_POS) && joystick == event.jaxis.which && event.jaxis.axis == joyItem)
            oldAxis = event.jaxis.value;
        else
            return false;
        return (type == JOYAXIS_NEG && event.jaxis.value <= -deadZone)
               || (type == JOYAXIS_POS && event.jaxis.value >= deadZone);
    }
    if(event.type == SDL_JOYHATMOTION)
    {
        switch(type)
        {
            case JOYHAT_UP:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    oldHat = event.jhat.value;
                    return (distinctHatDirections? event.jhat.value == SDL_HAT_UP : event.jhat.value & SDL_HAT_UP);
                }
                break;
            case JOYHAT_DOWN:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    oldHat = event.jhat.value;
                    return (distinctHatDirections? event.jhat.value == SDL_HAT_DOWN : event.jhat.value & SDL_HAT_DOWN);
                }
                break;
            case JOYHAT_LEFT:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    oldHat = event.jhat.value;
                    return (distinctHatDirections? event.jhat.value == SDL_HAT_LEFT : event.jhat.value & SDL_HAT_LEFT);
                }
                break;
            case JOYHAT_RIGHT:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    oldHat = event.jhat.value;
                    return (distinctHatDirections? event.jhat.value == SDL_HAT_RIGHT : event.jhat.value & SDL_HAT_RIGHT);
                }
                break;
            case JOYHAT_LEFTUP:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    oldHat = event.jhat.value;
                    return (distinctHatDirections? event.jhat.value == SDL_HAT_LEFTUP : event.jhat.value & SDL_HAT_LEFTUP);
                }
                break;
            case JOYHAT_LEFTDOWN:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    oldHat = event.jhat.value;
                    return (distinctHatDirections? event.jhat.value == SDL_HAT_LEFTDOWN : event.jhat.value & SDL_HAT_LEFTDOWN);
                }
                break;
            case JOYHAT_RIGHTUP:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    oldHat = event.jhat.value;
                    return (distinctHatDirections? event.jhat.value == SDL_HAT_RIGHTUP : event.jhat.value & SDL_HAT_RIGHTUP);
                }
                break;
            case JOYHAT_RIGHTDOWN:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    oldHat = event.jhat.value;
                    return (distinctHatDirections? event.jhat.value == SDL_HAT_RIGHTDOWN : event.jhat.value & SDL_HAT_RIGHTDOWN);
                }
                break;
            default:
                break;
        }

    }
    return false;
}


bool Button::checkUp(const SDL_Event& event)
{
    if(event.type == SDL_KEYUP)
    {
        return (type == KEY && event.key.keysym.sym == key);
    }
    if(event.type == SDL_MOUSEBUTTONUP)
    {
        return (type == MOUSEBUTTON && /*event.button.which == mouse &&*/ event.button.button == mouseButton);
    }

    if(joystick < 0 || joystick >= numjoysticks || joysticks == NULL)
        return false;

    if(event.type == SDL_JOYBUTTONUP)
    {
        return (type == JOYBUTTON && joystick == event.jbutton.which && event.jbutton.button == joyItem);
    }
    // Axis in opposite dir (or less than deadZone)...  Hat in another dir...
    if(event.type == SDL_JOYAXISMOTION)
    {
        return (type == JOYAXIS_NEG && joystick == event.jaxis.which && event.jaxis.axis == joyItem && oldAxis <= -deadZone && event.jaxis.value > -deadZone)
               || (type == JOYAXIS_POS && joystick == event.jaxis.which && event.jaxis.axis == joyItem && oldAxis >= deadZone && event.jaxis.value < deadZone);
    }
    if(event.type == SDL_JOYHATMOTION)
    {
        switch(type)
        {
            case JOYHAT_UP:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    if(distinctHatDirections)
                        return (oldHat == SDL_HAT_UP && event.jhat.value != SDL_HAT_UP);
                    else
                        return (oldHat & SDL_HAT_UP && !(event.jhat.value & SDL_HAT_UP));
                }
                break;
            case JOYHAT_DOWN:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    if(distinctHatDirections)
                        return (oldHat == SDL_HAT_DOWN && event.jhat.value != SDL_HAT_DOWN);
                    else
                        return (oldHat & SDL_HAT_DOWN && !(event.jhat.value & SDL_HAT_DOWN));
                }
                break;
            case JOYHAT_LEFT:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    if(distinctHatDirections)
                        return (oldHat == SDL_HAT_LEFT && event.jhat.value != SDL_HAT_LEFT);
                    else
                        return (oldHat & SDL_HAT_LEFT && !(event.jhat.value & SDL_HAT_LEFT));
                }
                break;
            case JOYHAT_RIGHT:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    if(distinctHatDirections)
                        return (oldHat == SDL_HAT_RIGHT && event.jhat.value != SDL_HAT_RIGHT);
                    else
                        return (oldHat & SDL_HAT_RIGHT && !(event.jhat.value & SDL_HAT_RIGHT));
                }
                break;
            case JOYHAT_LEFTUP:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    if(distinctHatDirections)
                        return (oldHat == SDL_HAT_LEFTUP && event.jhat.value != SDL_HAT_LEFTUP);
                    else
                        return (oldHat & SDL_HAT_LEFTUP && !(event.jhat.value & SDL_HAT_LEFTUP));
                }
                break;
            case JOYHAT_LEFTDOWN:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    if(distinctHatDirections)
                        return (oldHat == SDL_HAT_LEFTDOWN && event.jhat.value != SDL_HAT_LEFTDOWN);
                    else
                        return (oldHat & SDL_HAT_LEFTDOWN && !(event.jhat.value & SDL_HAT_LEFTDOWN));
                }
                break;
            case JOYHAT_RIGHTUP:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    if(distinctHatDirections)
                        return (oldHat == SDL_HAT_RIGHTUP && event.jhat.value != SDL_HAT_RIGHTUP);
                    else
                        return (oldHat & SDL_HAT_RIGHTUP && !(event.jhat.value & SDL_HAT_RIGHTUP));
                }
                break;
            case JOYHAT_RIGHTDOWN:
                if(joystick == event.jhat.which && event.jhat.hat == joyItem)
                {
                    if(distinctHatDirections)
                        return (oldHat == SDL_HAT_RIGHTDOWN && event.jhat.value != SDL_HAT_RIGHTDOWN);
                    else
                        return (oldHat & SDL_HAT_RIGHTDOWN && !(event.jhat.value & SDL_HAT_RIGHTDOWN));
                }
                break;
            default:
                break;
        }

        return false;
    }
    return false;
}

/*
Returns the current value of the button.  This value ranges from 0
to 32767.
*/
#define MAX_STATE_VALUE 32767

Uint16 Button::getState()
{
    if(type != KEY && type != MOUSEBUTTON && (joystick < 0 || joystick >= numjoysticks || joysticks == NULL))
        return 0;

    Sint16 temp;
    switch(type)
    {
        case KEY:
            return (keystates != NULL && keystates[key]? MAX_STATE_VALUE : 0);
            break;

        case MOUSEBUTTON:
            return ((SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(mouseButton))? MAX_STATE_VALUE : 0);

        case JOYBUTTON:
            return (SDL_JoystickGetButton(joysticks[joystick], joyItem)? MAX_STATE_VALUE : 0);
            break;

        case JOYAXIS_NEG:
            temp = SDL_JoystickGetAxis(joysticks[joystick], joyItem);
            if(temp >= 0)
                return 0;
            if(temp < -MAX_STATE_VALUE)
                return MAX_STATE_VALUE;
            return -temp;
            break;
        case JOYAXIS_POS:
            temp = SDL_JoystickGetAxis(joysticks[joystick], joyItem);
            if(temp <= 0)
                return 0;
            if(temp > MAX_STATE_VALUE)
                return MAX_STATE_VALUE;
            return temp;
            break;

        case JOYHAT_UP:
            if(distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_UP : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_UP)
                return MAX_STATE_VALUE;
            return 0;
            break;
        case JOYHAT_DOWN:
            if(distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_DOWN : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_DOWN)
                return MAX_STATE_VALUE;
            return 0;
            break;
        case JOYHAT_LEFT:
            if(distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_LEFT : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_LEFT)
                return MAX_STATE_VALUE;
            return 0;
            break;
        case JOYHAT_RIGHT:
            if(distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_RIGHT : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_RIGHT)
                return MAX_STATE_VALUE;
            return 0;
            break;
        case JOYHAT_LEFTUP:
            if(distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_LEFTUP : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_LEFTUP)
                return MAX_STATE_VALUE;
            return 0;
            break;
        case JOYHAT_LEFTDOWN:
            if(distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_LEFTDOWN : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_LEFTDOWN)
                return MAX_STATE_VALUE;
            return 0;
            break;
        case JOYHAT_RIGHTUP:
            if(distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_RIGHTUP : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_RIGHTUP)
                return MAX_STATE_VALUE;
            return 0;
            break;
        case JOYHAT_RIGHTDOWN:
            if(distinctHatDirections? SDL_JoystickGetHat(joysticks[joystick], joyItem) == SDL_HAT_RIGHTDOWN : SDL_JoystickGetHat(joysticks[joystick], joyItem) & SDL_HAT_RIGHTDOWN)
                return MAX_STATE_VALUE;
            return 0;
            break;
        default:
            break;
    }
    return 0;
}







// Checks the Buttons or sets the mouse position each frame
// Also piles up deltas if events are not being used.
void Cursor::update(float dt)
{
    my_dt = dt;
    bool speedup = false;
    if(type == BUTTONS)
    {
        bool up = button[0].checkHold();
        bool down = button[1].checkHold();
        bool left = button[2].checkHold();
        bool right = button[3].checkHold();
        if(up && !(down && stopOnOppositePress))
        {
            y -= vel*dt;
            speedup = true;
        }
        else if(down && !(up && stopOnOppositePress))
        {
            y += vel*dt;
            speedup = true;
        }
        if(left && !(right && stopOnOppositePress))
        {
            x -= vel*dt;
            speedup = true;
        }
        else if(right && !(left && stopOnOppositePress))
        {
            x += vel*dt;
            speedup = true;
        }
    }
    else if(type == MOUSE && updateMouseByState)
    {
        // Select mouse index here...
        // No acceleration here.
        if(!useRelativeMouse)
        {
            SDL_GetMouseState(&x, &y);
        }
        else if(relativeOneForOne)
        {
            // This looks silly, but it is a little different than absolute positions.
            SDL_GetMouseState(&dx, &dy);
            dx = dx - x;
            dy = dy - y;
            x += dx;
            y += dy;
        }
        else
        {
            SDL_GetMouseState(&dx, &dy);
            dx = dx - x;
            dy = dy - y;

            if(dx != 0 || dy != 0)
            {
                float dir = atan2(dy, dx);
                x += cos(dir)*vel*dt;
                y += sin(dir)*vel*dt;
            }

            speedup = true;
        }
    }
    else if(type == TRACKBALL && updateTrackballByState && joystick < numjoysticks)
    {
        SDL_JoystickGetBall(joysticks[joystick], trackball, &dx, &dy);
        if(relativeOneForOne)
        {
            x += dx;
            y += dy;
        }
        else if(dx != 0 || dy != 0)
        {
            float dir = atan2(dy, dx);
            x += cos(dir)*vel*dt;
            y += sin(dir)*vel*dt;

            speedup = true;
        }
    }

    if(speedup)
    {
        vel += accel*dt;
        if(vel > maxVel)
        {
            vel = maxVel;
        }
    }
    else  // If you're not speeding up, you're stopping.
    {
        vel = minVel;
    }
}

void Cursor::update(const SDL_Event& event)  // Uses the mouse or ball
{
    bool speedup = false;
    if(type == MOUSE && !updateMouseByState)
    {
        if(event.type == SDL_MOUSEMOTION)
        {
            // Check mouse index...
            if(useRelativeMouse)
            {
                // Apply velocities somehow?
                dx = event.motion.xrel;
                dy = event.motion.yrel;
                if(relativeOneForOne)
                {
                    x += dx;
                    y += dy;
                }
                else
                {
                    if(dx != 0 || dy != 0)
                    {
                        float dir = atan2(dy, dx);
                        x += cos(dir)*vel*my_dt;
                        y += sin(dir)*vel*my_dt;
                    }

                    speedup = true;
                }
            }
            else
            {
                dx = event.motion.xrel;
                dy = event.motion.yrel;
                x = event.motion.x;
                y = event.motion.y;
            }
        }
    }
    else if(type == TRACKBALL && !updateTrackballByState)
    {
        if(event.type == SDL_JOYBALLMOTION)
        {
            if(event.jball.which == joystick && event.jball.ball == trackball)
            {
                dx = event.jball.xrel;
                dy = event.jball.yrel;
                if(relativeOneForOne)
                {
                    x += dx;
                    y += dy;
                }
                else if(dx != 0 || dy != 0)
                {
                    float dir = atan2(dy, dx);
                    x += cos(dir)*vel*my_dt;
                    y += sin(dir)*vel*my_dt;

                    speedup = true;
                }
            }
        }
    }

    if(speedup)
    {
        vel += accel*my_dt;
        if(vel > maxVel)
        {
            vel = maxVel;
        }
    }
    else  // If you're not speeding up, you're stopping.
    {
        vel = minVel;
    }
}









DirectionSet::State::State()
    : dirX(0), dirY(0), magnitude(0)
{}

DirectionSet::DirectionSet()
{}

DirectionSet::DirectionSet(const Button& up, const Button& down, const Button& left, const Button& right)
    : up(up), down(down), left(left), right(right)
{
    update();
}

void DirectionSet::update(bool circular)
{
    float upVal = fabs(up.getState());
    float downVal = fabs(down.getState());
    float leftVal = fabs(left.getState());
    float rightVal = fabs(right.getState());
    
    // Offset by the deadZone
    upVal -= up.deadZone;
    downVal -= down.deadZone;
    leftVal -= left.deadZone;
    rightVal -= right.deadZone;
    
    if(upVal < 0.0f)
        upVal = 0.0f;
    if(downVal < 0.0f)
        downVal = 0.0f;
    if(leftVal < 0.0f)
        leftVal = 0.0f;
    if(rightVal < 0.0f)
        rightVal = 0.0f;
    
    float capX = MAX_STATE_VALUE;
    float capY = MAX_STATE_VALUE;
    float minDeadZone = 0;
    
    // Find the directions to use and their deadZones
    if(leftVal > rightVal)
    {
        state.dirX = -leftVal;
        capX -= left.deadZone;
        minDeadZone = left.deadZone;
    }
    else
    {
        state.dirX = rightVal;
        capX -= right.deadZone;
        minDeadZone = right.deadZone;
    }
    if(upVal > downVal)
    {
        state.dirY = upVal;
        capY -= up.deadZone;
        if(up.deadZone < minDeadZone)
            minDeadZone = up.deadZone;
    }
    else
    {
        state.dirY = -downVal;
        capY -= down.deadZone;
        if(down.deadZone < minDeadZone)
            minDeadZone = down.deadZone;
    }
    
    
    // Store the normalized amount in x and y directions separately
    float x = fabs(state.dirX)/capX;
    float y = fabs(state.dirY)/capY;
    
    // Get the direction
    float angle = atan2(state.dirY, state.dirX);
    state.dirX = cos(angle);
    state.dirY = sin(angle);
    
    // Calculate the magnitude based on the amount of each button in its own direction
    float fx = fabs(state.dirX);
    float fy = fabs(state.dirY);
    
    state.magnitude = (x*fx + y*fy)/(fx + fy);  // Weighted average based on direction
    
    if(state.magnitude > 1.0f)
        state.magnitude = 1.0f;
    
}










void loadDefaultController(Controller& controller)
{
    controller.buttons.clear();
    controller.buttons.push_back(Button(SDLK_UP));
    controller.buttons.push_back(Button(SDLK_DOWN));
    controller.buttons.push_back(Button(SDLK_LEFT));
    controller.buttons.push_back(Button(SDLK_RIGHT));
    controller.buttons.push_back(Button(SDLK_SPACE));
    controller.buttons.push_back(Button(SDLK_RETURN));
}

void loadDefaultController(Controller& controller, unsigned int joystick)
{
    controller.buttons.clear();
    controller.buttons.push_back(Button(Button::JOYAXIS_NEG, joystick, 1));
    controller.buttons.push_back(Button(Button::JOYAXIS_POS, joystick, 1));
    controller.buttons.push_back(Button(Button::JOYAXIS_NEG, joystick, 0));
    controller.buttons.push_back(Button(Button::JOYAXIS_POS, joystick, 0));
    controller.buttons.push_back(Button(Button::JOYBUTTON, joystick, 1));
    controller.buttons.push_back(Button(Button::JOYBUTTON, joystick, 0));
}



Controller::Controller()
{}
Controller::Controller(unsigned int joystick)
{}

unsigned int Controller::addButton(const Button& button)
{
    buttons.push_back(button);
    return buttons.size() - 1;
}

void Controller::setButton(unsigned int buttonIndex, const Button& button)
{
    while(buttons.size() <= buttonIndex)
        buttons.push_back(Button());
    buttons[buttonIndex] = button;
}

Button& Controller::getButton(unsigned int buttonIndex)
{
    if(buttonIndex >= buttons.size())
        throw std::logic_error("Tried to getButton() out of range.");
    return buttons[buttonIndex];
}

void Controller::deleteButtons()
{
    buttons.clear();
}

bool Controller::checkHold(unsigned int buttonIndex)
{
    if(buttonIndex >= buttons.size())
        throw std::logic_error("Tried to checkHold() out of range.");
    return buttons[buttonIndex].checkHold();
}


unsigned int Controller::checkInput(SDL_Event& event, bool checkDown)
{
    if(checkDown)
    {
        for(unsigned int i = 0; i < buttons.size(); i++)
        {
            if(buttons[i].checkDown(event))
                return i;
        }
    }
    else
    {
        for(unsigned int i = 0; i < buttons.size(); i++)
        {
            if(buttons[i].checkUp(event))
                return i;
        }
    }

    return NO_BUTTON;
}






} // END namespace SI

